;(function() {
  /**
   * addClassName to an Element
   * @param {HTMLElement} node
   */
  const addClassName = (node, str) => {
    if (node.className.split(' ').filter(s => s === str).length === 0) {
      node.className += ` ${str}`
    }
  }
  /**
   *
   * @param {HTMLElement} node
   */
  const removeClassName = (node, str) => {
    node.className = node.className
      .split(' ')
      .filter(s => s !== str)
      .join(' ')
  }
  /**
   * 设定边界值 Set Max and Min
   * @param {number} num
   * @param {number} max
   * @param {number} min
   */
  const numberBorder = (num, max, min) => Math.max(Math.min(num, max), min)

  /**
   * 转换rgb颜色到hsb
   * @param {string} hex
   */
  const rgbToHsb = hex => {
    const hsb = { h: 0, s: 0, b: 0 }
    if (hex.indexOf('#') === 0) {
      hex = hex.substring(1)
    }
    if (hex.length === 3) {
      hex = hex
        .split('')
        .map(s => s + s)
        .join('')
    }
    if (hex.length !== 6) return false
    hex = [hex.substr(0, 2), hex.substr(2, 2), hex.substr(4, 2)].map(s =>
      parseInt(s, 16)
    )
    const rgb = {
      r: hex[0],
      g: hex[1],
      b: hex[2]
    }
    const MAX = Math.max(...hex)
    const MIN = Math.min(...hex)
    //H start
    if (MAX === MIN) {
      hsb.h = 0
    } else if (MAX === rgb.r && rgb.g >= rgb.b) {
      hsb.h = (60 * (rgb.g - rgb.b)) / (MAX - MIN) + 0
    } else if (MAX === rgb.r && rgb.g < rgb.b) {
      hsb.h = (60 * (rgb.g - rgb.b)) / (MAX - MIN) + 360
    } else if (MAX === rgb.g) {
      hsb.h = (60 * (rgb.b - rgb.r)) / (MAX - MIN) + 120
    } else if (MAX === rgb.b) {
      hsb.h = (60 * (rgb.r - rgb.g)) / (MAX - MIN) + 240
    }
    //H end
    if (MAX === 0) {
      hsb.s = 0
    } else {
      hsb.s = 1 - MIN / MAX
    }
    hsb.b = MAX / 255
    return hsb
  }

  /**
   * 由百分比转为一个基准rgb颜色 Percent to RGB
   * @param {number} heightPercent 当前选中位置相对整体高度
   * @returns {r: any,g: any,b: any}
   */
  const heightToRgb = heightPercent => {
    heightPercent = 1 - heightPercent
    let rgb = { r: undefined, g: undefined, b: undefined }
    const percentInEach = heightPercent * 6
    return Object.entries(rgb).reduce(
      (lastObj, nowArr, index) =>
        Object.assign(lastObj, {
          [nowArr[0]]: Math.floor(
            (function() {
              const left = ((index + 1) % 3) * 2
              const right = left + 2
              const differenceL = percentInEach - left
              const differenceR = right - percentInEach
              if (differenceL >= 0 && differenceR >= 0) {
                return 0
              }
              const distance = Math.min(
                Math.abs(differenceL),
                Math.abs(differenceR),
                Math.abs(6 - differenceL),
                Math.abs(6 - differenceR)
              )
              return Math.min(255, 255 * distance)
            })()
          )
        }),
      {}
    )
  }

  const heightAddLAndT_ToRGB = (height, left, top) => {
    const rgb = heightToRgb(height)
    for (const key in rgb) {
      rgb[key] = (255 - rgb[key]) * (1 - left) + rgb[key]
      rgb[key] = rgb[key] * (1 - top)
    }
    return rgb
  }

  const rgbToHex = rgb => {
    const { r, g, b } = rgb
    return (
      Math.floor(r)
        .toString(16)
        .padStart(2, '0') +
      Math.floor(g)
        .toString(16)
        .padStart(2, '0') +
      Math.floor(b)
        .toString(16)
        .padStart(2, '0')
    )
  }

  /**
   * Hex To RGB
   * @param {string} hex
   */
  const hexToRgb = hex => {
    return {
      r: parseInt(hex.substr(0, 2), 16),
      g: parseInt(hex.substr(2, 2), 16),
      b: parseInt(hex.substr(4, 2), 16)
    }
  }

  /**
   * "cE" is only for zip codes
   * @param {HTMLElement} str
   */
  const cE = str => document.createElement(str)

  class ColorPicker {
    constructor({ dom = cE('div'), input, value = 'FFF' } = {}) {
      this.dom = dom
      const thisClass = this

      Array.prototype.forEach.call(this.getDOM().children, node => {
        node.remove()
      })
      addClassName(dom, 'color-picker')

      const rightBar = cE('div')
      rightBar.className = 'color-picker-right-bar'
      const rightBarPicker = cE('div')
      rightBarPicker.className = 'color-picker-right-bar-picker'

      rightBar.appendChild(rightBarPicker)

      const colorBlock = cE('div')
      colorBlock.className = 'color-picker-color-block'
      const gradientColor = cE('div')
      gradientColor.className =
        'color-picker-gradients color-picker-gradient-color'
      const gradientBlack = cE('div')
      gradientBlack.className =
        'color-picker-gradients color-picker-gradient-black'

      gradientColor.style.background =
        'linear-gradient(to right,#FFFFFF,#FF0000)'

      const gradientCircle = cE('div')
      gradientCircle.className = 'color-picker-circle'

      gradientBlack.appendChild(gradientCircle)

      const textInput = cE('input')
      const textInputBox = cE('div')
      textInputBox.className = 'color-picker-input'
      textInput.maxLength = 6
      textInput.style.width = '100%'
      textInput.style.height = '100%'
      textInput.type = 'text'
      textInputBox.appendChild(textInput)

      this.getDOM().appendChild(rightBar)
      this.getDOM().appendChild(colorBlock)
      this.getDOM().appendChild(textInputBox)
      this.getDOM().appendChild(gradientColor)
      this.getDOM().appendChild(gradientBlack)

      textInput.addEventListener('change', () => {
        this.setValue(textInput.value, true)
        this.onchange()
        this.updatePicker()
      })

      this.textInput = textInput
      this._gradientBlack = gradientBlack
      this._gradientColor = gradientColor
      this._rightBar = rightBar
      this._rightBarPicker = rightBarPicker
      this._colorBlock = colorBlock

      this._gradientCircle = gradientCircle

      this._height = 0
      this._mouseX = 0
      this._mouseY = 0

      this.setValue(value, true)
      this._lastValue = this.value
      this.updatePicker()
      //this.input = input

      const mouseMoveFun = e => {
        window.addEventListener('mouseup', function mouseUpFun() {
          thisClass.getDOM().style.userSelect = 'text'
          window.removeEventListener('mousemove', mouseMoveFun)
          window.removeEventListener('mouseup', mouseUpFun)
        })
        const bbox = thisClass._gradientBlack.getBoundingClientRect()
        this._mouseX = e.clientX - bbox.left // * (p.width / bbox.width)
        this._mouseY = e.clientY - bbox.top // * (p.height / bbox.height)
        this.mouseBorder()
        this.setValue(
          heightAddLAndT_ToRGB(this.height, this.position.x, this.position.y)
        )
        this.updatePicker()
      }
      const mouseMoveFunBar = e => {
        window.addEventListener('mouseup', function mouseUpFunBar() {
          thisClass.getDOM().style.userSelect = 'text'
          window.removeEventListener('mousemove', mouseMoveFunBar)
          window.removeEventListener('mouseup', mouseUpFunBar)
        })
        const bbox = thisClass._rightBar.getBoundingClientRect()
        this._height = e.clientY - bbox.top // * (p.height / bbox.height)
        this.mouseBorderBar()
        this.setValue(
          heightAddLAndT_ToRGB(this.height, this.position.x, this.position.y)
        )
        this.updatePicker()
      }
      this._gradientBlack.addEventListener('mousedown', e => {
        this.getDOM().style.userSelect = 'none'
        mouseMoveFun(e)
        window.addEventListener('mousemove', mouseMoveFun)
      })
      this._rightBar.addEventListener('mousedown', e => {
        this.getDOM().style.userSelect = 'none'
        mouseMoveFunBar(e)
        window.addEventListener('mousemove', mouseMoveFunBar)
      })

      if ('ontouchstart' in window) {
        const touchFun = e => {
          e.preventDefault()
          e = e.touches[0]
          const bbox = thisClass._gradientBlack.getBoundingClientRect()
          this._mouseX = e.clientX - bbox.left // * (p.width / bbox.width)
          this._mouseY = e.clientY - bbox.top // * (p.height / bbox.height)
          this.mouseBorder()
          this.setValue(
            heightAddLAndT_ToRGB(this.height, this.position.x, this.position.y)
          )
          this.updatePicker()
        }
        const touchFunBar = e => {
          e.preventDefault()
          e = e.touches[0]
          const bbox = this._rightBar.getBoundingClientRect()
          this._height = e.clientY - bbox.top // * (p.height / bbox.height)
          this.mouseBorderBar()
          this.setValue(
            heightAddLAndT_ToRGB(this.height, this.position.x, this.position.y)
          )
          this.updatePicker()
        }
        this._gradientBlack.addEventListener('touchmove', touchFun)
        this._gradientBlack.addEventListener('touchstart', touchFun)
        this._rightBar.addEventListener('touchmove', touchFunBar)
        this._rightBar.addEventListener('touchstart', touchFunBar)
      }

      this._changeFunctions = []
      //this.updateValue()
    }
    onchange() {
      this._changeFunctions.forEach(fun =>
        fun({
          target: this,
          type: 'change',
          timeStamp: performance.now()
        })
      )
    }
    /**
     *
     * @param {"change"} type
     * @param {function} fun
     */
    addEventListener(type, fun) {
      if (typeof fun !== 'function') {
        return
      }
      switch (type) {
        case 'change': {
          this._changeFunctions.push(fun)
          break
        }
      }
    }

    getValue(mode = 'value') {
      switch (mode) {
        case 'hex': {
          return this._value
        }
        case 'rgb': {
          return hexToRgb(this.getValue('hex'))
        }
        case 'hsb': {
          return rgbToHsb(this.getValue('hex'))
        }
        case 'value':
        default: {
          return '#' + this._value
        }
      }
    }
    getBrightness() {
      const { r, g, b } = this.getValue('rgb')
      return 0.299 * r + 0.587 * g + 0.114 * b
    }
    setValue(value, resetPosition = false) {
      let hex = ''
      switch (typeof value) {
        case 'string': {
          if (value.indexOf('#') === 0) {
            value = value.substring(1)
          }
          if (value.length === 3) {
            value = value
              .split('')
              .map(s => s + s)
              .join('')
          }
          if (value.length !== 6) {
            value = 'FFFFFF'
          }
          hex = value
          break
        }
        case 'object': {
          hex = rgbToHex(value)
        }
      }
      let rgb
      try {
        rgb = hexToRgb(hex)
      } catch (error) {
        rgb = {
          r: 255,
          g: 255,
          b: 255
        }
      }
      const { r, g, b } = rgb
      this._value = rgbToHex({ r, g, b }).toUpperCase()
      this.textInput.value = this._value
      this._colorBlock.style.backgroundColor = this.getValue()
      if (resetPosition) {
        const { h, s, b } = rgbToHsb(hex)
        this._height = 1 - h / 360
        if (h === 0) this._height = 0
        this._mouseX = s
        this._mouseY = 1 - b
      } else {
        if (this._lastValue !== this.value) {
          this.onchange()
        }
      }
      this._lastValue = this.value
    }
    /**
     * @return {HTMLDivElement}
     */
    getDOM() {
      return this.dom
    }
    mouseBorder() {
      this._mouseX = numberBorder(
        this._mouseX / (this._gradientBlack.getBoundingClientRect().width - 2),
        1,
        0
      )
      this._mouseY = numberBorder(
        this._mouseY / (this._gradientBlack.getBoundingClientRect().height - 2),
        1,
        0
      )
    }
    mouseBorderBar() {
      this._height = numberBorder(
        this._height / (this._rightBar.getBoundingClientRect().height - 2),
        1,
        0
      )
    }
    updatePicker() {
      const position = this.position
      const target = this._gradientCircle
      target.style.left = `${position.x * 100}%`
      target.style.top = `${position.y * 100}%`
      this._rightBarPicker.style.top = `${this.height * 100}%`
      this._gradientColor.style.background = `linear-gradient(to right,#FFFFFF,#${rgbToHex(
        heightToRgb(this.height)
      )})`
      if (this.getBrightness() > 152) {
        addClassName(target, 'color-picker-circle-black')
        removeClassName(target, 'color-picker-circle-white')
      } else {
        removeClassName(target, 'color-picker-circle-black')
        addClassName(target, 'color-picker-circle-white')
      }
    }
    get position() {
      return {
        x: this._mouseX,
        y: this._mouseY
      }
    }
    get height() {
      return this._height
    }
    get value() {
      return this.getValue()
    }
    set value(value) {
      this.setValue(value, true)
      this.updatePicker()
    }
  }
  if (module && typeof exports === 'object') {
    // For Node, CommonJS
    module.exports.ColorPicker = ColorPicker
  } else if (typeof window === 'object') {
    window.ColorPicker = ColorPicker
  }
})()
